﻿/*  ----------------------------------------------------------------------------
 *  DES-Release 2
 *  ----------------------------------------------------------------------------
 *  File:       Executor.cs
 *  Author:     nitinkoshy@gmail.com
 *  Source:     http://des.codeplex.com/
 *  ----------------------------------------------------------------------------
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DES.Common;
using System.Threading;
using System.Reflection;
using System.IO;
using System.Configuration;
using System.Diagnostics;
using System.Security.Principal;
using System.Security.Policy;
using System.Runtime.Remoting;
using DES.Server.Service.Helpers;

namespace DES.Server.Service.Workers
{
    /// <summary>
    /// picks up the 
    /// </summary>
    public class Executor : WorkerBase
    {
        private  string _defaultLocation = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location);

        public override void PerformTask(DESServerService serverService)
        {
            ServerTask nextTask;
            if (serverService.TaskQueue.Count == 0)
                return;

            Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
            Trace.WriteLine(String.Format("Executor Message : TaskQueue count {0}", serverService.TaskQueue.Count));
            lock (serverService.TaskQueue)
            {
                nextTask = serverService.TaskQueue.Dequeue();
            }

            try
            {
                //need to mark this task's children for execution
                if (nextTask.Task.HasChildren)
                {
                    Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                    Trace.WriteLine(String.Format("Executor Message : Expanding child tasks for {0}", nextTask.Task.Name));
                    lock (serverService.TaskQueue)
                    {
                        foreach (ClientTask childTask in nextTask.Task.ChildTasks)
                        {
                            serverService.TaskQueue.Enqueue(
                                new ServerTask(childTask)
                                );
                        }
                    }

                    //make an entry to maintain the server 
                    //at which the children were got split - used later during collection
                    nextTask.ChildTasksMarkedAtServer = serverService.ServerInfo;
                }

                if (nextTask.Task.CanExecuteIndependently)
                {
                    Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                    Trace.WriteLine(String.Format("Executor Message : Executing task {0}", nextTask.Task.Name));
                    object result = ExecuteTask(nextTask.Task);
                    nextTask.Task.TaskResult = result;
                }
                else
                    throw new NotImplementedException("Dependent task execution not supported/implemented");
            }
            catch (Exception E)
            {
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Executor Message : Error while executing task {0}", nextTask.Task.Name));
                nextTask.Task.GeneratedError = true;
                nextTask.Task.ExecutionError = E.Message;
                nextTask.Task.TaskResult = null;
                nextTask.Task.ExecutionComplete = true;

                if (E.InnerException != null)
                    nextTask.Task.ExecutionError = E.Message + E.InnerException.Message;
            }

            
            //should it go to the collection gate or to the forwarder?
            if (CollectionGateKeeper.ShouldTaskWait(nextTask, serverService))
            {
                lock (serverService.CollectionGate)
                {
                    serverService.CollectionGate.Add(nextTask);
                    nextTask.Task.ExecutionComplete = true;//this is important to be here.
                }
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Executor Message : task should wait : {0}", nextTask.Task.Name));
                CollectionGateKeeper.DumpCollectionGate(serverService);
            }
            else
            {
                //move to the processed queue

                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("Executor Message : moved task {0} to processed queue", nextTask.Task.Name));
                nextTask.Task.ExecutionComplete = true;
                lock (serverService.ProcessedTasks)
                {
                    serverService.ProcessedTasks.Enqueue(nextTask);
                }
            }
        }

        public override string GetWorkerName()
        {
            return "Executor";
        }

        public override int GetThreadSleepTime()
        {
            return Convert.ToInt32(ConfigurationManager.AppSettings["executorthreadsleeptime"]);
        }
        

        public object ExecuteTask(ClientTask task)
        {
            WindowsPrincipal windowsPrincipal = null;
            windowsPrincipal = SSPIHelper.LogonUser(task.Credential);
            if (windowsPrincipal == null)
                throw new ApplicationException("Executor Message : Invalid Credentials Provided");

            string assemblySaveLocation = Path.Combine(_defaultLocation, task.AssemblyName) + ".dll";

            if (!File.Exists(task.AssemblyFileLocation))
                return null;

            //check if the assembly file name matches a core assembly name.
            if (!File.Exists(assemblySaveLocation))
                File.Copy(task.AssemblyFileLocation, assemblySaveLocation);
                 //File.WriteAllBytes(assemblySaveLocation, task.AssemblyBytes);

            //execution has to happen in the context of the passed in credentials
            Object returnData = null;

            WindowsIdentity newId = (WindowsIdentity)windowsPrincipal.Identity;
            WindowsImpersonationContext impersonatedUser = newId.Impersonate();
            Trace.Write(String.Format("[{0}]:", DateTime.Now.ToShortTimeString()));
            Trace.WriteLine(String.Format("Executor Message : Impersonated as {0}", newId.Name));
            try
            {
                //create the application domain and create an instance of the object
                AppDomain clientDomain = AppDomain.CreateDomain("ClientTaskDomain");
                try
                {
                    AssemblyLoader _aLoader = (AssemblyLoader)clientDomain.CreateInstanceAndUnwrap("DES.Server.Service", "DES.Server.Service.Helpers.AssemblyLoader");
                    returnData = _aLoader.LoadAndRun(task.AssemblyName, task.TypeName);
                }
                finally
                {
                    AppDomain.Unload(clientDomain);
                }
            }
            finally
            {
                impersonatedUser.Undo();//back to the normal a/c
                File.Delete(assemblySaveLocation);
            }

            return returnData;
        }

    }


}
